package com.alibaba.cirtus.ots.application.adapter.ability.facade.service.sap;


import com.alibaba.cirtus.ots.application.adapter.ability.utils.InfoUtil;
import com.alibaba.citrus.ots.common.util.JsonUtil;
import com.alibaba.citrus.ots.common.util.QuantityFormatUtils;
import com.alibaba.citrus.ots.common.util.IntegrateUtils;
import com.alibaba.citrus.ots.common.util.SapAndCrmFieldDealUtils;
import com.alibaba.citrus.ots.common.annotation.ProcessInvoker;
import com.alibaba.citrus.ots.common.exception.FacadeException;
import com.alibaba.cz.base.tool.DateFormatUtil;
import com.alibaba.cz.base.tool.feature.Features;
import com.alibaba.cz.base.tool.message.ErrorCodeException;
import com.alibaba.fastjson.JSON;
import com.alibaba.lattice2.epoch.sdk.model.Result;
import com.epoch.app.bcots.model.dto.*;
import com.epoch.app.bcots.service.BaseDataService;
import com.epoch.app.bcots.service.BcOtsService;
import com.epoch.app.otsapplicationadaptersdk.materialsyncretry.model.MaterialSyncRetry;
import com.epoch.app.otsapplicationadaptersdk.materialsyncretry.service.MaterialSyncRetryService;
import com.epoch.app.otsapplicationadaptersdk.model.dto.MaterialSyncRetryLoadListRequest;
import com.epoch.app.otsapplicationadaptersdk.model.dto.MessageHeaderSDO;
import com.epoch.app.otsapplicationadaptersdk.model.dto.SaleOrderForMaterialRequest;
import com.epoch.app.otsapplicationadaptersdk.sap.*;
import com.epoch.app.otsapplicationadaptersdk.sap.mnsapif004.service.MnSapIf004Service;
import com.epoch.app.otsapplicationadaptersdk.service.OtsApplicationAdapterSdkService;
import com.epoch.app.otsb2btradecenter.api.orderwrite.service.OrderWriteService;
import com.epoch.app.otsb2btradecenter.api.sandoutgoods.service.SandOutGoodsService;
import com.epoch.app.otsb2btradecenter.dto.Packagedetails;
import com.epoch.app.otsb2btradecenter.dto.request.GenerateOrderRequest;
import com.epoch.app.otsb2btradecenter.dto.request.ScItemDetail;
import com.epoch.app.otsb2btradecenter.dto.request.SendOutGoodsRequest;
import com.epoch.app.otsb2btradecenter.dto.response.DeliveryOrderModifyResponse;
import com.epoch.app.otsunifiedinventory.api.allocateorderwrite.service.AllocateOrderWriteService;
import com.epoch.app.otsunifiedinventory.api.forecastwarehouse.dto.QueryWarehouseByFactoryCodeAndWarehouseCodeRequest;
import com.epoch.app.otsunifiedinventory.api.forecastwarehouse.service.ForecastWarehouseService;
import com.epoch.app.otsunifiedinventory.domain.allocateorder.dto.AllocateOrderLoadListRequest;
import com.epoch.app.otsunifiedinventory.domain.allocateorder.model.AllocateOrder;
import com.epoch.app.otsunifiedinventory.domain.allocateorder.service.AllocateOrderService;
import com.epoch.app.otsunifiedinventory.dto.request.AllocateOrderReceivedRequest;
import com.epoch.app.otsunifiedinventory.dto.request.AllocateOrderReceivedScItemDTO;
import com.epoch.app.otsunifiedinventory.dto.request.AllocateOrderShippedRequest;
import com.epoch.app.otsunifiedinventory.dto.request.AllocateOrderShippedScItemDTO;
import com.epoch.app.otsunifiedinventory.dto.response.AllocateOrderDetailsQueryResponse;
import com.epoch.app.otsunifiedinventory.dto.response.AllocateOrderReceivedResponse;
import com.epoch.app.otsunifiedinventory.dto.response.AllocateOrderShippedResponse;
import com.epoch.app.otsunifiedinventory.materialretry.dto.GetFailRecordBySAPMertrailIdRequest;
import com.epoch.app.otsunifiedinventory.materialretry.model.MaterialRetry;
import com.epoch.app.otsunifiedinventory.materialretry.service.MaterialRetryService;
import com.epoch.app.otsunifiedinventory.model.dto.AllocateOrderCancelReceivedRequest;
import com.epoch.app.otsunifiedinventory.model.dto.AllocateOrderCancelReceivedResponse;
import com.epoch.app.otsunifiedinventory.model.dto.AllocateOrderCancelShippedRequest;
import com.epoch.app.otsunifiedinventory.model.dto.AllocateOrderCancelShippedResponse;
import com.epoch.app.otsunifiedinventory.model.dto.AllocateOrderCancelShippedScItemDTO;
import com.epoch.app.otsunifiedinventory.model.dto.AllocateOrderQueryRequest;
import com.epoch.app.otsunifiedinventory.model.dto.MaterialRetryLoadListRequest;
import com.epoch.app.otsunifiedinventory.model.dto.MaterialTypes;
import com.epoch.app.otsunifiedinventory.model.dto.SaleOrderForMaterial;
import com.epoch.app.otsunifiedinventory.model.dto.WarehouseInfoDTO;
import com.epoch.app.otsunifiedinventory.service.OtsUnifiedInventoryService;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.cz.base.tool.log.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author hanguo
 * @date 2021/9/25
 * @Description 物料凭证同步接口
 */
@Component
public class MnSapIf004ServiceImpl implements MnSapIf004Service {
    private Log log = Log.getLogger(MnSapIf004ServiceImpl.class);
    @Autowired
    private AllocateOrderWriteService allocateOrderWriteService;
    @Autowired
    private SandOutGoodsService sandOutGoodsService;
    @Autowired
    private OtsUnifiedInventoryService otsUnifiedInventoryService;
    @Resource
    private BaseDataService baseDataService;
    @Resource
    private BcOtsService bcOtsService;
    @Resource
    private OtsApplicationAdapterSdkService otsApplicationAdapterSdkService;
    @Resource
    private OrderWriteService orderWriteService;
    @Resource
    ForecastWarehouseService forecastWarehouseService;
    @Resource
    MaterialRetryService retryService;
    @Resource
    AllocateOrderService allocateOrderService;
    @Resource
    private MaterialSyncRetryService materialSyncRetryService;

    private static final List<String> excludeSapTypes = Lists.newArrayList("601","602");
    //销售单元仓类型为3
    private static final String salesWarehouseType = "3";
    //牛要客销售组织
    private static final String NYK_ORG_ID = "298622740826374971";

    /**
     * 物料凭证都一条条给。 为了兼容其他接口。request里面是list
     *
     * @param mnCrmIf004Request
     * @return
     */
    @Override
    @ProcessInvoker(remark = "MnSapIf004Service")
    public MnSapCommResponse externalSyncMaterial(MnSapIf004Request mnCrmIf004Request) {
        log.warn("MnSapIf004ServiceImpl.externalSyncMaterial,param:{}",mnCrmIf004Request==null?"":JSON.toJSONString(mnCrmIf004Request));
        CreatOutinOrderRequest outin = new CreatOutinOrderRequest();
        List<MnSapIf004SDO> dataList = mnCrmIf004Request.getData();
        if (CollectionUtils.isEmpty(dataList)) {
            String dataJson= IntegrateUtils.safeGetDataJson();
            if (StringUtils.isEmpty(dataJson)){
                throw new FacadeException("OTS-03-001-01-15-004");
            }
            dataList = JSON.parseArray(dataJson, MnSapIf004SDO.class);
            if (CollectionUtils.isEmpty(dataList)){
                throw new FacadeException("OTS-03-001-01-15-004");
            }
        }
        MnSapIf004SDO sapMaterial = dataList.get(0);
        /**
         *         {
         * [{
         *       "HeaderKey": "HeaderKey 原样返回",
         *       "STATUS": "S/E，S成功，E失败",
         *       "ERRCODE": "错误代码",
         *       "MESSAGE": "消息描述",
         *       "EXT_ID": "订单平台内部业务ID",
         *       "Item": [
         *           "Item_key": "行项目ID，有行项目则原样返回",
         *           "STATUS": "S/E，S成功，E失败",
         *           "MESSAGE": "消息描述",
         *         }
         *       ]
         *     }] 组装返回参数
         */
        MnSapCommResponse mnSapCommResponse = new MnSapCommResponse();
        MessageHeaderSDO messageHeader = mnCrmIf004Request.getMessageHeader();
        if (Objects.isNull(messageHeader)){
             messageHeader = IntegrateUtils.safeGetMessageHeader(MessageHeaderSDO.class);
        }
        if (Objects.nonNull(messageHeader)){
            mnSapCommResponse.setInterfacePath(messageHeader.getInterfacePath());
            mnSapCommResponse.setMessageId(messageHeader.getMessageId());
        }

        //凭证的返回list
        List<MnSapCommSDO> responseList = new ArrayList<>();
        //凭证下批次的返回list
        List<MnSapCommDetail> itemError = new ArrayList<>();
        MnSapCommSDO mnSapCommSDO = new MnSapCommSDO();
        mnSapCommSDO.setHeaderKey(sapMaterial.getHeaderKey());
        mnSapCommSDO.setStatus("S");
        try {
            outin = packageOutInOrderRequest(outin, sapMaterial, itemError);
            //是否是冲销的 冲销的
            if (outin != null) {
                String isReverseStr = outin.getFeatures().getFeatureMap().get("isReverse");
                Boolean isReverse = StringUtils.isBlank(isReverseStr) ? false : Boolean.valueOf(isReverseStr);
                String hasInorderStr = outin.getFeatures().getFeatureMap().get("hasInOrder");
                Boolean hasInorder = StringUtils.isBlank(hasInorderStr) ? false : Boolean.valueOf(hasInorderStr);
                if (isReverse) {
                    /**
                     * 2021.11.23 新增冲销功能。
                     * 1.出入库单照旧记录
                     * 2.根据交货单号，找到原调拨单和出入库单
                     * 3.原出入库单状态 改为已冲销
                     * 4.原出入库单的库存变更还原
                     * 5.只有出的类型明细会进行冲销，入的报错出去
                     * 2021.12.13 新增入库冲销
                     *  1.入库的冲销不通知销售单，只变更库存
                     *  2.入库类型的冲销，通知调拨单
                     *  2021.12.16
                     *    1.销售退货 冲销入库
                     *    2.销售换货  不通知销售单冲销
                     *
                     */
                    reverseOutinOrder(outin, sapMaterial,hasInorder,mnCrmIf004Request);
                } else {
                    log.warn("MnSapIf004ServiceImpl.externalSyncMaterial bcOtsService.createOutinOrders,request:{}",JSON.toJSONString(outin));
                    Result<WarehousingOrderOperationResponse> result = bcOtsService.createOutinOrders(outin);
                    log.warn("MnSapIf004ServiceImpl.externalSyncMaterial bcOtsService.createOutinOrders,result{}",result==null?"":JSON.toJSONString(result));
                    if (result.isSuccess()) {
                        //通知调拨 销售单 改状态 通知创建 销售单
                        afterInventory(outin,sapMaterial.getOutMaterialCertificateId());
                    }

                    if (!result.isSuccess()) {
                        saveRetryLogs(sapMaterial.getOutMaterialCertificateId(), JSON.toJSONString(mnCrmIf004Request));
                        throw new FacadeException("OTS-03-002-01-15-012","请前往系统日志搜索并重试接口");
                    }
                    //如果调用成功了 有外部单号 但是找不到调拨单/销售单 就放入重试 2022.05.27
                    // 2022.06.07 排除移动类型为601 or 602，根据工厂+库存地点关联我们的逻辑仓库为销售单元仓，同时销售单元仓对应的销售组织为6140
                    //2022.06.14 需要重试物料过多,其中有些单据sap一直不同步过来, 重试有问题, 暂时先去除限制了
                    //2022.07.25 放入临时表中,而非重试表
                    if (result.isSuccess()){
                        if (isBeforeSourceOrder(outin,sapMaterial)){
                            //加入临时表
                            saveMaterialRetry(mnCrmIf004Request,sapMaterial);
//                        throw new FacadeException("OTS-03-002-01-15-112");
                        }else {
                            //变更临时表数据
                            updateMaterialRetry(mnCrmIf004Request,sapMaterial);
                        }

                    }
                }
            }
            mnSapCommSDO.setItem(itemError);
            responseList.add(mnSapCommSDO);
            mnSapCommResponse.setData(responseList);

        } catch (ErrorCodeException e) {
            //如果是货品找不到 放入重试
//            if (e != null &&
//                    ("OTS-03-001-01-15-002".equals(e.getErrorCode()) || "OTS-03-001-01-15-015".equals(e.getErrorCode())
//                    || "OTS-03-002-01-15-112".equals(e.getErrorCode()))){
//            }
            //2022.05.20 把这些异常都加入重试表
            saveRetryLogs(sapMaterial.getOutMaterialCertificateId(), JSON.toJSONString(mnCrmIf004Request));
            mnSapCommSDO.setStatus("E");
            log.error("物料凭证ErrorCodeException:" + sapMaterial.getOutMaterialCertificateId() + "报错",e);
            throw e;
        } catch (Throwable e) {
            mnSapCommSDO.setStatus("E");
            saveRetryLogs(sapMaterial.getOutMaterialCertificateId(), JSON.toJSONString(mnCrmIf004Request));
            log.error("物料凭证Exception:" + sapMaterial.getOutMaterialCertificateId() + "报错,errorMsg:"+e.getMessage(), e);
            throw new FacadeException("OTS-03-002-01-15-010");
        } finally {
            log.warn("MnSapIf004ServiceImpl.externalSyncMaterial,response:{}",JSON.toJSONString(mnSapCommResponse));
        }
        return mnSapCommResponse;
    }

    /**
     * 判断004接口是否在调拨单和销售单之前, 如果是就报错重新放入重试表
     * @param request
     * @param sdo
     * @return
     */
    private boolean isBeforeSourceOrder(CreatOutinOrderRequest request,MnSapIf004SDO sdo){
        EpochFeatures features = request.getFeatures();
        Map<String, String> featureMap = features.getFeatureMap();
        if (featureMap == null){
            return false;
        }
        String isAllocate = features.getFeatureMap().get("isAllocate");
        if ("0".equals(isAllocate) && !StringUtils.isAllBlank(sdo.getOutSourceOrderId(),sdo.getOutAllocateOrderId())){
            //2022.06.07 排除移动类型为601 or 602，根据工厂+库存地点关联我们的逻辑仓库为销售单元仓，同时销售单元仓对应的销售组织为6140
//            if (CollectionUtils.isNotEmpty(request.getGoods())){
//                List<CreateOutinOrderGoodsRequest> goods = request.getGoods();
//                for (CreateOutinOrderGoodsRequest good : goods) {
//                    String sapType = good.getSapType();
//                    String warehouseAreaType = good.getWarehouseAreaType();
//                    if (excludeSapTypes.contains(sapType) && salesWarehouseType.equals(warehouseAreaType)
//                        && NYK_ORG_ID.equals(good.getWarehouseAreaOrgId())){
//                        return false;
//                    }
//                }
//            }
            return true;
//            //判断下是否是牛要客的销售单 如果是牛要客的 就跳过 因为牛要客的订单 外部编码存在了nykid上
//            AllocateOrderLoadListRequest allocateOrderLoadListRequest = new AllocateOrderLoadListRequest();
//            if (StringUtils.isNotBlank(sdo.getOutSourceOrderId())){
//                allocateOrderLoadListRequest.setNykId(sdo.getOutSourceOrderId());
//            }
//            if (StringUtils.isNotBlank(sdo.getOutAllocateOrderId())){
//                allocateOrderLoadListRequest.setNykId(sdo.getOutAllocateOrderId());
//            }
//            Result<List<AllocateOrder>> loadAllocateOrderList = allocateOrderService.loadAllocateOrderList(allocateOrderLoadListRequest);
//            if (loadAllocateOrderList != null && CollectionUtils.isNotEmpty(loadAllocateOrderList.getResult())){
//                return false;
//            }
//            return true;
        }
        return false;
    }

    /**
     * 组装物料凭证 批次请求信息
     *
     * @param subTmp
     * @param outin
     * @return
     */
    private CreateOutinOrderGoodsRequest packageItem(MnSapIf004Detail subTmp, CreatOutinOrderRequest outin, Features outinFeatures, Map<String, ScItemSDO> itemSDOMap, MnSapIf004SDO mateial) {
        CreateOutinOrderGoodsRequest subOrder = new CreateOutinOrderGoodsRequest();
        subOrder.setGoodsId(itemSDOMap.get(subTmp.getScItemId()).getScItemId());
        subOrder.setBatchNo(subTmp.getBatchId());
        subOrder.setGoodsName(itemSDOMap.get(subTmp.getScItemId()).getTitle());
        subOrder.setGoodsUnit(subTmp.getBaseUnit());
        subOrder.setSourceSubOrderId(outin.getSourceWarehousingOrderId());
        subOrder.setLineNo(subTmp.getOutLineId());
        //设置销售单外部行号
        subOrder.setOutSourceOrderLineId(InfoUtil.deleteZeroPrefix(subTmp.getOutSourceOrderLineId()));
        //调拨单外部行号 去掉前缀0
        subOrder.setOutAllocateOrderLineId(InfoUtil.deleteZeroPrefix(subTmp.getOutAllocateOrderLineId()));

        try {
            //数量乘1000处理落库  乘1000的只针对基本单位数量操作。 给销售单的是销售数量不做处理
            //通知调拨销售单的数量都不做1000处理  只是请求落库存的时候需要乘1000
            BigDecimal d = QuantityFormatUtils.formatQuantityForDB(subTmp.getBaseUnitQuantity().trim());
            subOrder.setGoodsQuantity(d.longValue());
        } catch (Exception e) {
            log.error("格式化数量错误",e);
            throw new FacadeException("OTS-03-001-01-15-003", subTmp.getBatchId(), subTmp.getBaseUnitQuantity());
        }
        try {
            if (StringUtils.isBlank(SapAndCrmFieldDealUtils.generateIdField(subTmp.getProductionTime())) || StringUtils.isBlank(subTmp.getProductionTime())) {
                subOrder.setDom(DateFormatUtil.parse("20000101"));
            } else {
                //时间转换一下格式 sap发送的格式为yyyyMMdd
                subOrder.setDom(DateFormatUtil.parse(subTmp.getProductionTime()));
            }
        } catch (Exception e) {
            log.error("日期转换错误",e);
            throw new FacadeException("OTS-03-001-01-15-007");
        }
        Features subFeatures = Features.newFeatures(subTmp.getFeatures());
        subFeatures.addFeature("lineNo", subTmp.getOutLineId());
        subFeatures.addFeature("outSourceOrderLineId", subTmp.getOutSourceOrderLineId());
        subFeatures.addFeature("outDeliveryOrderLineId", subTmp.getOutDeliveryOrderLineId());
        subFeatures.addFeature("outAllocateOrderLineId", subTmp.getOutAllocateOrderLineId());
        subFeatures.addFeature("outDeliveryOrderId", mateial.getOutDeliveryOrderId());
        subFeatures.addFeature("goodsCode", subTmp.getScItemId());
        subFeatures.addFeature("saleUnitQuantity", subTmp.getSaleUnitQuantity());
        subFeatures.addFeature("saleUnit", subTmp.getSaleUnit());
        subFeatures.addFeature("baseUnit", subTmp.getBaseUnit());
        subFeatures.addFeature("baseUnitQuantity", subTmp.getBaseUnitQuantity());
        subFeatures.addFeature("isAllocate", outinFeatures.get("isAllocate"));
        subFeatures.addFeature("sourceWarehouingOrderCode", outinFeatures.get("sourceWarehouingOrderCode"));
        subOrder.setOutSubOrderId(mateial.getOutMaterialCertificateId());
        subOrder.setDcSignal(subTmp.getDcSignal());
        subOrder.setSapType(subTmp.getSapType());
        if (StringUtils.isBlank(subTmp.getFactoryCode())) {
            throw new FacadeException("OTS-03-002-01-15-015");
        }
        WarehouseInfoDTO warehouseInfo = searchWarehouseByCode(subTmp);
        subFeatures.addFeature("warehouseType",warehouseInfo.getType());
        //设置逻辑仓库类型 仓库的销售组织
        subOrder.setWarehouseAreaType(warehouseInfo.getType()==null?null:warehouseInfo.getType().toString());
        subOrder.setWarehouseAreaOrgId(warehouseInfo.getSaleOrgId());
        subOrder.setProperties(subFeatures.toString());
            //id是逻辑仓id  warehouseId是仓库id
        subOrder.setWarehouseAreaId(warehouseInfo.getId());
        subOrder.setWarehouseId(warehouseInfo.getWarehouseId());
        //如果是车销仓，没有parentCode，但是wms  库存那里又需要写warehouseId，就把warehouseAreaId赋值进去。
        //不然可能会报错
        if(StringUtils.isEmpty(warehouseInfo.getWarehouseId())){
            subOrder.setWarehouseId(warehouseInfo.getId());
        }
        return subOrder;
    }

    /**
     * 拼接 查仓库的请求
     *
     * @param subTmp
     * @return
     */
    private WarehouseInfoDTO searchWarehouseByCode(MnSapIf004Detail subTmp) {
        /**
         * 物料凭证里过来的明细里的仓库  通过factoryCode（对应内部的仓库id）和inventoryCode匹配唯一仓库，  因为库存code 可能重复。
         * 1.有通过factoryCode inventoryCode 直接匹配仓库
         * 2.只有factoryCode 没有inventoryCode，通过只有factoryCode查到唯一下属的逻辑仓（如果有多个下属逻辑仓报错出去）
         */
        QueryWarehouseByFactoryCodeAndWarehouseCodeRequest query = new QueryWarehouseByFactoryCodeAndWarehouseCodeRequest();
        query.setFactoryCode(subTmp.getFactoryCode());
        if (StringUtils.isBlank(subTmp.getInventoryCode())) {
            query.setWarehouseCode("DEFAULT");
        } else {
            query.setWarehouseCode(subTmp.getInventoryCode());
        }
        return searchWarehouse(query);
    }

    private WarehouseInfoDTO searchWarehouse(QueryWarehouseByFactoryCodeAndWarehouseCodeRequest query) {
        Result<List<WarehouseInfoDTO>> listResult = forecastWarehouseService.queryWarehouseByFactoryCodeAndWarehouseCode(query);
        if (CollectionUtils.isEmpty(listResult.getResult())) {
            log.error("搜索库存，query=" + JSON.toJSONString(query));
            throw new FacadeException("OTS-03-001-01-15-001","仓库Code:"+query.getFactoryCode()+"库存地点code:"+ query.getWarehouseCode());
        }
        return listResult.getResult().get(0);
    }

    /**
     * 组装物料凭证主信息
     *
     * @param outin
     * @param sapMaterial
     * @return
     */
    private CreatOutinOrderRequest packageOutInOrderRequest(CreatOutinOrderRequest outin, MnSapIf004SDO sapMaterial, List<MnSapCommDetail> itemError) {
        Features features = Features.newFeatures(sapMaterial.getFeatures());
        outin.setOutWarehousingOrderId(sapMaterial.getOutMaterialCertificateId());
        features.addFeature("outSourceOrderId", sapMaterial.getOutSourceOrderId());
        features.addFeature("outAllocateOrderId", sapMaterial.getOutAllocateOrderId());
        features.addFeature("outDeliveryOrderId", sapMaterial.getOutDeliveryOrderId());
        features.addFeature("postingTime", sapMaterial.getPostingTime());
        features.addFeature("shipType", sapMaterial.getShipType());
        features.addFeature("shipNo", sapMaterial.getShipNo());
        features.addFeature("shipPhone", sapMaterial.getShipPhone());
        features.addFeature("shipContact", sapMaterial.getShipContact());
        features.addFeature("expectedDate", sapMaterial.getExpectedDate());
        features.addFeature("createTime", sapMaterial.getCreateTime());
        //是否工厂直发商家 是的话 要调用创建销售单的接口  只有调拨单考虑该选项。
        boolean isF2B = false;
        //判断是否是冲销类型， 是冲销类型，整个物料凭证都按冲销算.
        //且根据明细里的Ext字段查原物料凭证
        boolean isReverse = false;
        //0 无销售和调拨单号  1.是调拨单  2是销售单
        //销售 、调拨单号 要么2选1存在 要么都为空。 不会两个都有
        int isAllocate = 1;
        String sourceWarehouingOrderId = "";
        String sourceWarehouingOrderCode = "";
        String sourceWarehouseArea = "";
        String targetWarehouseArea = "";
        String sourceWarehouse = "";
        String targetWarehouse = "";
        String saleOrderTag="normal";
        long sumQuantity = 0;
        if (StringUtils.isBlank(sapMaterial.getOutAllocateOrderId()) && StringUtils.isBlank(sapMaterial.getOutSourceOrderId())) {
            isAllocate = 0;
            //如果是0 ，子单必须都有仓库号
        } else {
            /**
             *   1204 PD改需求， 有没有调拨/销售单或有调拨/销售单号无论能否查到  库存都落库  又调拨单/销售单号才通知
             *   如果销售调拨单号查到的销售调拨单是空的， isallocate按0处理
             */
            if (StringUtils.isNotBlank(sapMaterial.getOutAllocateOrderId())) {
                isAllocate = 1;
                AllocateOrderDetailsQueryResponse allocateResponse = getAllocatedOrder(sapMaterial.getOutAllocateOrderId());
                //对象不为空，但是对象里面都是空的
                if (!checkAllocatedOrderNotBlank(allocateResponse)) {
                    isAllocate = 0;
                } else {
                    if (allocateResponse.getIsDTC() != null && allocateResponse.getIsDTC()) {
                        isF2B = true;
                    }
                    //出入库单 和调拨单互查 用自己的内部id
                    sourceWarehouingOrderId = String.valueOf(allocateResponse.getId());
                    sourceWarehouingOrderCode = allocateResponse.getCode();
                    //调拨退单要特殊处理，调拨退单的收发货仓和正向单据的收发货仓是一致的，所以实际执行退单的出入库时要把收发货仓反过来
                    if (BooleanUtils.isTrue(allocateResponse.getReturnOrder())) {
                        sourceWarehouseArea = allocateResponse.getReceiveAreaId();
                        targetWarehouseArea = allocateResponse.getShipAreaId();
                        sourceWarehouse = allocateResponse.getReceiveWarehouseId();
                        targetWarehouse = allocateResponse.getShipWarehouseId();
                    } else {
                        sourceWarehouseArea = allocateResponse.getShipAreaId();
                        targetWarehouseArea = allocateResponse.getReceiveAreaId();
                        sourceWarehouse = allocateResponse.getShipWarehouseId();
                        targetWarehouse = allocateResponse.getReceiveWarehouseId();
                    }

                }
            }
            /**
             * SAP用outSourceOrderId给过来的外部id，可能是我们的销售单也可能是调拨单。
             * SAP用outAllocateOrderId给过来的外部id，肯定是调拨单。
             */
            if (StringUtils.isNotBlank(sapMaterial.getOutSourceOrderId())) {
                //查是否有对应的销售单
                SaleOrderForMaterialRequest query = new SaleOrderForMaterialRequest();
                query.setOuterOrderId(sapMaterial.getOutSourceOrderId());
                Result<List<SaleOrderForMaterial>> result = otsApplicationAdapterSdkService.saleOrderForMaterial(query);
                SaleOrderForMaterial saleOrderResponse = null;
                if (!CollectionUtils.isEmpty(result.getResult())) {
                    saleOrderResponse = result.getResult().get(0);
                    sourceWarehouingOrderId = String.valueOf(saleOrderResponse.getId());
                    sourceWarehouingOrderCode = saleOrderResponse.getOrderBizId();
                    sourceWarehouseArea = saleOrderResponse.getWarehouseAreaId();
                    /**
                     *    @see com.alibaba.citrus.ots.b2b.trade.order.facade.common.SetOrderTag
                     */
                    saleOrderTag=saleOrderResponse.getOrderTag();
                    isAllocate = 2;
                }
                AllocateOrderDetailsQueryResponse allocateResponse = null;
                if (StringUtils.isBlank(sourceWarehouingOrderId)) {
                    //根据销售单查不到时 再查调拨单
                    //查是否有对应的调拨单
                    allocateResponse = getAllocatedOrder(sapMaterial.getOutSourceOrderId());
                    //出入库单 和调拨单互查 用自己的内部id
                    if (checkAllocatedOrderNotBlank(allocateResponse)) {
                        isAllocate = 1;
                        sourceWarehouingOrderId = String.valueOf(allocateResponse.getId());
                        sourceWarehouingOrderCode = allocateResponse.getCode();
                        //这俩id是内部id
                        //调拨退单要特殊处理，调拨退单的收发货仓和正向单据的收发货仓是一致的，所以实际执行退单的出入库时要把收发货仓反过来
                        if (BooleanUtils.isTrue(allocateResponse.getReturnOrder())) {
                            sourceWarehouseArea = allocateResponse.getReceiveAreaId();
                            targetWarehouseArea = allocateResponse.getShipAreaId();
                            sourceWarehouse = allocateResponse.getReceiveWarehouseId();
                            targetWarehouse = allocateResponse.getShipWarehouseId();
                        } else {
                            sourceWarehouseArea = allocateResponse.getShipAreaId();
                            targetWarehouseArea = allocateResponse.getReceiveAreaId();
                            sourceWarehouse = allocateResponse.getShipWarehouseId();
                            targetWarehouse = allocateResponse.getReceiveWarehouseId();
                        }
                        if (allocateResponse.getIsDTC() != null && allocateResponse.getIsDTC()) {
                            isF2B = true;
                        }
                    }
                }
                /**
                 * 产品确定的，销售调拨单号无论是否查到 ，都落库
                 */
                if (saleOrderResponse == null && !checkAllocatedOrderNotBlank(allocateResponse)) {
                    isAllocate = 0;
                    // throw new FacadeException("OTS-03-001-15-006", sapMaterial.getOutSourceOrderId());
                }
            }
        }
        //在系统中查找调拨单 或销售单。  查不到。 将isAllocate置为0. 直接操作库存
        //查到调拨/销售单，将单子里的仓库-货区赋值到features里
        //SAP物料凭证里的明细里的仓库给的是code   调拨-销售单里存的是逻辑仓的id
        outin.setSourceWarehousingOrderId(sourceWarehouingOrderId);
        features.addFeature("isAllocate", isAllocate);
        features.addFeature("isF2B", isF2B);
        features.addFeature("saleOrderTag",saleOrderTag);
        features.addFeature("sourceWarehouingOrderCode", sourceWarehouingOrderCode);
        if (isAllocate > 0) {
            //当前明细要操作的仓库
            features.addFeature("sourceWarehouseArea", sourceWarehouseArea);
            features.addFeature("sourceWarehouse", sourceWarehouse);
            if (isAllocate == 1) {
                //有在途需要增加时，target里放目标仓库信息
                features.addFeature("targetWarehouseArea", targetWarehouseArea);
                features.addFeature("targetWarehouse", targetWarehouse);
            }
        }
//        //2022.05.18 这里加一个校验 如果参数里有 调拨单/销售单的外部单号 但是isAllocate是0的 我们需要重试
//        if (isAllocate == 0 && !StringUtils.isAllBlank(sapMaterial.getOutAllocateOrderId(),sapMaterial.getOutSourceOrderId())){
//            throw new FacadeException("OTS-03-002-01-15-112");
//        }
        Result<List<MaterialTypes>> materialResult = otsUnifiedInventoryService.getMaterialTyps();
        List<MaterialTypes> materialTypesList = materialResult.getResult();
        Map<String, MaterialTypes> materialTypes = materialTypesList.stream()
                .collect(Collectors.toMap(x -> (x.getCodeType() + x.getOutInType()), x -> x, (v1, v2) -> v2));
        //先格式化一下物料凭证里的货品id 去一下0
        List<MnSapIf004Detail> itemsAfterFilter = sapMaterial.getItems().stream().filter(new Predicate<MnSapIf004Detail>() {
            @Override
            public boolean test(MnSapIf004Detail detail) {
                detail.setScItemId(SapAndCrmFieldDealUtils.subStringScItmCode(detail.getScItemId()));
                if (!StringUtils.isBlank(detail.getBatchId())) {
                    //批次id可能为空 在途的时候
                    detail.setBatchId(SapAndCrmFieldDealUtils.generateIdField(detail.getBatchId()));
                }
                if (!materialTypes.containsKey(detail.getSapType() + detail.getDcSignal())) {
                    //移动类型不识别的报错出去
                    throw new FacadeException("OTS-03-001-01-15-012", detail.getSapType() + ":" + detail.getDcSignal());
                }
                if (materialTypes.get(detail.getSapType() + detail.getDcSignal()).getInventoryType().intValue() == 1) {
                    log.warn("物料凭证明细在途跳过:物料凭证编号(" + sapMaterial.getOutMaterialCertificateId() + ")明细:(" + JSON.toJSONString(detail) + ")");
                    //在途的直接干掉不要了
                    return false;
                }

                return true;
            }
        }).collect(Collectors.toList());

        if (CollectionUtils.isEmpty(itemsAfterFilter)) {
            //如果批次明细里都是在途的，直接返回，告诉SAP处理完了。 不继续往下走代码了
            return null;
        }

        List<CreateOutinOrderGoodsRequest> outinSubOrders = new ArrayList<>();
        Map<String, ScItemSDO> scItems = getGoodsList(itemsAfterFilter);
        //冲销时，原物料凭证单号
        String originoutMaterialCertificateId="";
        //是否有入库类型的明细
        boolean hasInReverse=false;
        for (MnSapIf004Detail subTmp : itemsAfterFilter) {
            /**
             * 组装物料凭证里的批次
             */
            String itemKey = subTmp.getItem_key();
            MnSapCommDetail mnSapCommDetail = new MnSapCommDetail();
            mnSapCommDetail.setItemKey(itemKey);
            mnSapCommDetail.setStatus("S");
            //2022-03-15 宸之新提需求，如果批次id为空，自定一套。 遇到的情况为电商仓的物流凭证没带批次id
            if(StringUtils.isBlank(subTmp.getBatchId())){
                subTmp.setBatchId(defineBatchNo(subTmp.getScItemId(),subTmp.getFactoryCode(),subTmp.getInventoryCode(),subTmp.getProductionTime()));
            }
            try {
                checkParams(subTmp, materialTypes);
                //12.13新增入库可以冲销了， 入库类型的冲销只落库库存，不通知销售调拨单
                MaterialTypes types=materialTypes.get(subTmp.getSapType() + subTmp.getDcSignal());
                if(!hasInReverse){
                    hasInReverse= (types.getMainOrderType().intValue() ==2)
                            &&(types.getReverse().intValue() ==1);
                }
                if (!isReverse) {
                    //明细里有一条是是冲销类型，整个物料凭证都按冲销算
                    isReverse = (types.getReverse().intValue() == 1);
                }
                if(StringUtils.isNotBlank(subTmp.getExt())){
                    originoutMaterialCertificateId=subTmp.getExt();
                }
                CreateOutinOrderGoodsRequest subOrder = packageItem(subTmp, outin, features, scItems, sapMaterial);
                sumQuantity = sumQuantity + subOrder.getGoodsQuantity();
                outinSubOrders.add(subOrder);
            } catch (FacadeException e) {
                mnSapCommDetail.setMessage(e.getErrorMessage());
                mnSapCommDetail.setStatus("E");
                //这里就是阻断操作，不要乱动
                throw e;
            } finally {
                itemError.add(mnSapCommDetail);
            }
        }
        features.addFeature("sumQuantity", sumQuantity);
        features.addFeature("isReverse", isReverse);
        features.addFeature("hasInOrder",hasInReverse);
        if (isReverse) {
            features.addFeature("originoutMaterialCertificateId",originoutMaterialCertificateId);
        }
        EpochFeatures dtoFeature = new EpochFeatures();
        dtoFeature.setFeatureMap(features.getOriginMap());
        outin.setFeatures(dtoFeature);
        outin.setGoods(outinSubOrders);
        return outin;
    }

    /**
     * 自定义批次id
     * @param goodsCode
     * @param factoryCode
     * @param warehouseCode
     * @return
     */
    private String defineBatchNo(String goodsCode,String factoryCode,String warehouseCode,String dom){
        //EW 电商仓的缩写
        String defineBatchId="EW"+goodsCode+factoryCode+warehouseCode;
        if(StringUtils.isBlank(dom)){
                return defineBatchId;
        }
        return defineBatchId+dom;
    }
    /**
     * 校验批次的的不可为空的信息
     *
     * @param detail 批次信息
     * @return
     */
    private boolean checkParams(MnSapIf004Detail detail, Map<String, MaterialTypes> materialTypes) {
        if (StringUtils.isBlank(detail.getBatchId())) {
            //如果批次id为空， 并且不是在途类型的调拨。 则报错
            Integer inventoryType = materialTypes.get(detail.getSapType() + detail.getDcSignal()).getInventoryType();
            if (inventoryType == null) {
                throw new FacadeException("OTS-03-001-01-15-018");
            }
            if (inventoryType != null && inventoryType != 1) {
                throw new FacadeException("OTS-03-001-01-15-008");
            }
        }
        if (!materialTypes.containsKey(detail.getSapType() + detail.getDcSignal())) {
            throw new FacadeException("OTS-03-001-01-15-019");
        }
        if (StringUtils.isBlank(detail.getDcSignal())) {
            throw new FacadeException("OTS-03-001-01-15-009");
        }
        if (StringUtils.isBlank(detail.getSapType())) {
            throw new FacadeException("OTS-03-001-01-15-010");
        }
        //不校验日期了 如果日期为空 设置为2000年1月1日
//        if (StringUtils.isBlank(detail.getProductionTime())) {
//            throw new FacadeException("OTS-03-001-15-011");
//        }
//        if (StringUtils.isBlank(SapAndCrmFieldDealUtils.generateIdField(detail.getProductionTime()))) {
//            throw new FacadeException("OTS-03-001-15-014");
//        }
        return true;
    }

    /**
     * 获取货品列表，校验是否有该货品，并获取货品id，转换为出入库单创建参数时用
     *
     * @param items
     * @return
     */
    private Map<String, ScItemSDO> getGoodsList(List<MnSapIf004Detail> items) {
        Set<String> goodsCode = new HashSet<>();
        items.forEach(x -> goodsCode.add(x.getScItemId()));
        SearchScItemRequest searchScItemRequest = new SearchScItemRequest();
        Map<String, List<String>> outerIdList = new HashMap<>();
        outerIdList.put("outerIdList", goodsCode.stream().collect(Collectors.toList()));
        outerIdList.put("statusList", Arrays.asList("1","-1"));
        searchScItemRequest.setPageSize(items.size());
        searchScItemRequest.setSearchReqDTO(outerIdList);
        Result<List<ScItemSDO>> scItemResult = baseDataService.searchScItem(searchScItemRequest);
        if (scItemResult == null) {
            throw new FacadeException("OTS-03-001-01-15-002", JSON.toJSONString(goodsCode));
        }
        if (CollectionUtils.isEmpty(scItemResult.getResult())) {
            throw new FacadeException("OTS-03-001-01-15-015",  JSON.toJSONString(goodsCode));
        }
        Map<String, ScItemSDO> scItems = scItemResult.getResult().stream().collect(Collectors.toMap(x -> x.getOuterId(), x -> x, (v1, v2) -> v2));
        if (goodsCode.size() != scItemResult.getResult().size()) {
            Set<String> resultCode = new HashSet<>();
            scItemResult.getResult().forEach(x -> resultCode.add(x.getOuterId()));
            StringBuffer sb = new StringBuffer();
            items.forEach(x -> {
                if (!resultCode.contains(x.getScItemId())) {
                    sb.append(x.getScItemId() + ";");
                }
            });
            log.warn("MaterialHandle_GoodsCountNotMatch, scItemList:{}", sb.toString());
            throw new FacadeException("OTS-03-001-01-15-016",sb.toString());
        }
        return scItems;
    }

    /**
     * 获取调拨单
     *
     * @return
     */
    private AllocateOrderDetailsQueryResponse getAllocatedOrder(String outAllocateOrderId) {
        AllocateOrderQueryRequest allocateOrderQueryRequest = new AllocateOrderQueryRequest();
        allocateOrderQueryRequest.setOutAllocateOrderId(outAllocateOrderId);
        Result<AllocateOrderDetailsQueryResponse> allocateOrderResult = otsUnifiedInventoryService.allocateOrderQuery(allocateOrderQueryRequest);
        AllocateOrderDetailsQueryResponse allocateResponse = allocateOrderResult.getResult();
        return allocateResponse;
    }

    private boolean checkAllocatedOrderNotBlank(AllocateOrderDetailsQueryResponse allocateResponse) {
        if (allocateResponse != null && allocateResponse.getId() != null) {
            return true;
        }
        return false;
    }

    /**
     * 通知调拨单修改数量
     */
    private void notifyAllocated(String sourceOrderId, List<AllocateOrderShippedScItemDTO> outList, List<AllocateOrderReceivedScItemDTO> inList,String outMaterialCertificateId,String postingTime) {
        if (outList.size() > 0) {
            Result<AllocateOrderShippedResponse> rep = allocateOrderWriteService.shipped(AllocateOrderShippedRequest.builder()
                    .id(sourceOrderId)
                    .outMaterialCertificateId(outMaterialCertificateId)
                    .shippedScItemDTOS(outList).postingTime(postingTime).build());
            log.warn("MnSapIf004ServiceImpl.externalSyncMaterial bcOtsService.notifyAllocated,request:{}",JSON.toJSONString(rep));
            if (!rep.isSuccess()) {
                throw new FacadeException(rep.getErrorCode(), rep.getMessage());
            }
        }
        if (inList.size() > 0) {
            Result<AllocateOrderReceivedResponse> rep = allocateOrderWriteService.received(AllocateOrderReceivedRequest.builder()
                    .id(sourceOrderId)
                    .outMaterialCertificateId(outMaterialCertificateId)
                    .receivedScItemDTOS(inList).postingTime(postingTime).build());
            if (!rep.isSuccess()) {
                throw new FacadeException(rep.getErrorCode(), rep.getMessage());
            }
        }
    }

    /**
     * 通知销售单修改数量
     */
    private SendOutGoodsRequest getNotifySaleOrderRequest(CreatOutinOrderRequest sapMaterial) {
        SendOutGoodsRequest sendOutGoodsRequest = new SendOutGoodsRequest();
        sendOutGoodsRequest.setOrderId(sapMaterial.getSourceWarehousingOrderId());
        sendOutGoodsRequest.setDeliveryOrderId(sapMaterial.getFeatures().getFeatureMap().get("outDeliveryOrderId"));
        sendOutGoodsRequest.setOutMaterialCertificateId(sapMaterial.getOutWarehousingOrderId());
        String postingTime=sapMaterial.getFeatures().getFeatureMap().get("postingTime");
        sendOutGoodsRequest.setPostingTime(postingTime);
        List<Packagedetails> itemList = new ArrayList<>();
        List<CreateOutinOrderGoodsRequest> subOrderDOS = sapMaterial.getGoods();
        for (CreateOutinOrderGoodsRequest tmp : subOrderDOS) {
            Packagedetails packagedetails = new Packagedetails();
            Map<String, String> itemMap = new HashMap();
            //1201 张苏徽 让传参的key从货品id改为行号
            Features features = Features.newFeatures(tmp.getProperties());
            String linenoWithoutZero = SapAndCrmFieldDealUtils.generateIdField(features.get("outSourceOrderLineId"));
            //通知销售单 用销售单位数量， 别用基本单位数量。
            String notifyQuantity = features.get("saleUnitQuantity");
            itemMap.put(linenoWithoutZero, notifyQuantity);
            packagedetails.setOrderLineQuantity(itemMap);
            itemList.add(packagedetails);
        }
        sendOutGoodsRequest.setPackageDetailsList(itemList);
        return sendOutGoodsRequest;
    }

    /**
     * 创建完出入库单， 和库存变更完以后
     *
     * @param sapMaterial
     */
    private void afterInventory(CreatOutinOrderRequest sapMaterial,String outMaterialCertificateId) {
        String isAllocate = sapMaterial.getFeatures().getFeatureMap().get("isAllocate");
        String postingTime=sapMaterial.getFeatures().getFeatureMap().get("postingTime");
        boolean dealOnwayInventory = ("1").equals(isAllocate);
        boolean isSaleOrder = ("2").equals(isAllocate);
        String isF2BStr = sapMaterial.getFeatures().getFeatureMap().get("isF2B");
        boolean isF2B = !StringUtils.isBlank(isF2BStr) && Boolean.parseBoolean(isF2BStr);
        if (isSaleOrder) {
            SendOutGoodsRequest request = getNotifySaleOrderRequest(sapMaterial);
            Result<DeliveryOrderModifyResponse> resp = sandOutGoodsService.sandOutGoods(request);
            if (!resp.isSuccess()) {
                throw new FacadeException(resp.getErrorCode(), resp.getMessage());
            }
        }
        if (dealOnwayInventory) {
            List<CreateOutinOrderGoodsRequest> subOrderDOS = sapMaterial.getGoods();
            Result<List<MaterialTypes>> materialResult = otsUnifiedInventoryService.getMaterialTyps();
            List<MaterialTypes> materialTypesList = materialResult.getResult();
            List<ScItemDetail> createSaleList = getCreateSaleList(materialTypesList, subOrderDOS, isF2B);
            List<AllocateOrderShippedScItemDTO> outlist = getOutList(materialTypesList, subOrderDOS, isAllocate);
            List<AllocateOrderReceivedScItemDTO> inList = getInList(materialTypesList, subOrderDOS, isAllocate);
            notifyAllocated(sapMaterial.getSourceWarehousingOrderId(), outlist, inList,outMaterialCertificateId,postingTime);
            if (isF2B && createSaleList.size() > 0) {
                //创建销售单 新增需要传一下逻辑仓id
                String warehouseAreaId=subOrderDOS.get(0).getWarehouseAreaId();
                notifyCreateSaleOrder(sapMaterial.getSourceWarehousingOrderId(), createSaleList,warehouseAreaId,outMaterialCertificateId);
            }
        }
    }

    /**
     * 创建通知调拨单用的list
     *
     * @param subOrderDOS
     * @param isAllocate
     * @return
     */
    private List<AllocateOrderShippedScItemDTO> getOutList(List<MaterialTypes> materialTypesList, List<CreateOutinOrderGoodsRequest> subOrderDOS, String isAllocate) {
        List<AllocateOrderShippedScItemDTO> outList = new ArrayList<>();
        for (CreateOutinOrderGoodsRequest tmp : subOrderDOS) {
            MaterialTypes materialTypes = getMatrilaTypeByCodeAndType(materialTypesList, tmp.getSapType(), tmp.getDcSignal());
            if (materialTypes.getMainOrderType() == 2) {
                if ("1".equals(isAllocate)) {
                    AllocateOrderShippedScItemDTO outDto = new AllocateOrderShippedScItemDTO();
                    outDto.setScItemCode(tmp.getGoodsId());
                    outDto.setScItemId(tmp.getGoodsId());
                    //前面位了落库存 乘了1000处理。 这里要除回去
                    outDto.setShipQuantity(QuantityFormatUtils.getQuantityForPage(tmp.getGoodsQuantity().toString()).toString());
                    //加上外部单号
                    outDto.setOutAllocateOrderLineId(StringUtils.isBlank(tmp.getOutAllocateOrderLineId())?
                            tmp.getOutSourceOrderLineId() : tmp.getOutAllocateOrderLineId());
                    outList.add(outDto);
                }
            }
        }
        return outList;
    }

    private List<AllocateOrderCancelShippedScItemDTO> getReverseOutList(List<MaterialTypes> materialTypesList, List<CreateOutinOrderGoodsRequest> subOrderDOS, String isAllocate) {
        List<AllocateOrderCancelShippedScItemDTO> outList = new ArrayList<>();
        for (CreateOutinOrderGoodsRequest tmp : subOrderDOS) {
            MaterialTypes materialTypes = getMatrilaTypeByCodeAndType(materialTypesList, tmp.getSapType(), tmp.getDcSignal());
            if (materialTypes.getMainOrderType() == 1) {
                if ("1".equals(isAllocate)) {
                    AllocateOrderCancelShippedScItemDTO outDto = new AllocateOrderCancelShippedScItemDTO();
                    outDto.setScItemCode(tmp.getGoodsId());
                    outDto.setScItemId(tmp.getGoodsId());
                    //前面位了落库存 乘了1000处理。 这里要除回去
                    outDto.setShipQuantity(QuantityFormatUtils.getQuantityForPage(tmp.getGoodsQuantity().toString()).toString());
                    outDto.setOutAllocateOrderLineId(StringUtils.isBlank(tmp.getOutAllocateOrderLineId())?
                            tmp.getOutSourceOrderLineId() : tmp.getOutAllocateOrderLineId());
                    outList.add(outDto);
                }
            }
        }
        return outList;
    }
    private List<AllocateOrderReceivedScItemDTO> getReverseInList(List<MaterialTypes> materialTypesList, List<CreateOutinOrderGoodsRequest> subOrderDOS, String isAllocate) {
        List<AllocateOrderReceivedScItemDTO> inList = new ArrayList<>();
        for (CreateOutinOrderGoodsRequest tmp : subOrderDOS) {
            MaterialTypes materialTypes = getMatrilaTypeByCodeAndType(materialTypesList, tmp.getSapType(), tmp.getDcSignal());
            if (materialTypes.getMainOrderType() == 3) {
                //忽略 不变更库存。
                continue;
            }
            if (materialTypes.getMainOrderType() == 2) {
                if ("1".equals(isAllocate)) {
                    AllocateOrderReceivedScItemDTO outDto = new AllocateOrderReceivedScItemDTO();
                    outDto.setScItemCode(tmp.getGoodsId());
                    outDto.setScItemId(tmp.getGoodsId());
                    //前面位了落库存 乘了1000处理。 这里要除回去
                    outDto.setReceiveQuantity(QuantityFormatUtils.getQuantityForPage(tmp.getGoodsQuantity().toString()).toString());
                    //外部行号
                    outDto.setOutAllocateOrderLineId(StringUtils.isBlank(tmp.getOutAllocateOrderLineId())?
                            tmp.getOutSourceOrderLineId() : tmp.getOutAllocateOrderLineId());
                    inList.add(outDto);
                }
            }
        }
        return inList;
    }

    /**
     * 创建通知调拨单用的list
     *
     * @param subOrderDOS
     * @param isAllocate
     * @return
     */
    private List<AllocateOrderReceivedScItemDTO> getInList(List<MaterialTypes> materialTypesList, List<CreateOutinOrderGoodsRequest> subOrderDOS, String isAllocate) {
        List<AllocateOrderReceivedScItemDTO> inList = new ArrayList<>();
        for (CreateOutinOrderGoodsRequest tmp : subOrderDOS) {
            MaterialTypes materialTypes = getMatrilaTypeByCodeAndType(materialTypesList, tmp.getSapType(), tmp.getDcSignal());
            if (materialTypes.getMainOrderType() == 3) {
                //忽略 不变更库存。
                continue;
            }
            if (materialTypes.getMainOrderType() == 1) {
                //加库存
                if (("1").equals(isAllocate)) {
                    AllocateOrderReceivedScItemDTO inDto = new AllocateOrderReceivedScItemDTO();
                    inDto.setScItemCode(tmp.getGoodsId());
                    inDto.setScItemId(tmp.getGoodsId());
                    //前面位了落库存 乘了1000处理。 这里要除回去
                    inDto.setReceiveQuantity(QuantityFormatUtils.getQuantityForPage(tmp.getGoodsQuantity().toString()).toString());
                    //外部调拨单行号
                    inDto.setOutAllocateOrderLineId(StringUtils.isBlank(tmp.getOutAllocateOrderLineId())?
                            tmp.getOutSourceOrderLineId() : tmp.getOutAllocateOrderLineId());
                    inList.add(inDto);
                }
            }
        }
        return inList;
    }

    /**
     * 创建销售单用的list
     *
     * @param subOrderDOS
     * @param isF2B
     * @return
     */
    private List<ScItemDetail> getCreateSaleList(List<MaterialTypes> materialTypesList, List<CreateOutinOrderGoodsRequest> subOrderDOS, boolean isF2B) {
        List<ScItemDetail> createSaleList = new ArrayList<>();
        for (CreateOutinOrderGoodsRequest tmp : subOrderDOS) {
            MaterialTypes materialTypes = getMatrilaTypeByCodeAndType(materialTypesList, tmp.getSapType(), tmp.getDcSignal());
            if (materialTypes.getMainOrderType() == 1) {
                //加库存
                if (isF2B) {
                    ScItemDetail scItemDetail = new ScItemDetail();
                    Features features = Features.newFeatures(tmp.getProperties());
                    scItemDetail.setScItemId(tmp.getGoodsId());
                    scItemDetail.setSaleUnit(features.get("saleUnit"));
                    scItemDetail.setBaseUnitQuantity(features.get("baseUnitQuantity"));
                    scItemDetail.setBaseUnit(features.get("baseUnit"));
                    scItemDetail.setSaleUnitQuantity(features.get("saleUnitQuantity"));
                    createSaleList.add(scItemDetail);
                }
            }
        }
        return createSaleList;
    }

    /**
     * 根据物料凭证里的类型 获取是加还是减库存
     *
     * @param list
     * @param codeType
     * @param outinType
     * @return
     */
    public MaterialTypes getMatrilaTypeByCodeAndType(List<MaterialTypes> list, String codeType, String outinType) {
        for (MaterialTypes materialTypeDesc : list) {
            if (materialTypeDesc.getCodeType().trim().equals(codeType.trim()) && materialTypeDesc.getOutInType().equalsIgnoreCase(outinType)) {
                return materialTypeDesc;
            }
        }
        throw new FacadeException("OTS-03-001-01-15-020",codeType+":"+outinType);
    }

    /**
     * 通知创建销售单
     *
     * @param allocateId
     * @param createSaleList
     */
    public void notifyCreateSaleOrder(String allocateId, List<ScItemDetail> createSaleList,String warehouseAreaId,String outMaterialCertificateId) {
        GenerateOrderRequest generateOrderRequest = new GenerateOrderRequest();
        generateOrderRequest.setAllocateId(allocateId);
        generateOrderRequest.setScItemList(createSaleList);
        generateOrderRequest.setWareHouseAreaId(warehouseAreaId);
        generateOrderRequest.setOutMaterialCertificateId(outMaterialCertificateId);
        Result<Void> result = orderWriteService.generateOrder(generateOrderRequest);
        if (!result.isSuccess()) {
            throw new FacadeException(result.getErrorCode(), result.getMessage());
        }
    }

    /**
     * 冲销出入库单
     * 入库的冲销 不通知销售调拨单
     * isInReverse ture:入库的冲销，不通知调拨销售变更状态
     */
    public void reverseOutinOrder(CreatOutinOrderRequest outin, MnSapIf004SDO sapMaterial,boolean isInReverse,MnSapIf004Request mnCrmIf004Request) {
        //找不到原单也冲销，代码先注释，后期如果需要再放开
        //String deliveryOrderId = sapMaterial.getOutDeliveryOrderId();
//        GetOrigionOutinOrderRequest request = new GetOrigionOutinOrderRequest();
//        request.setDeliveryOrderId(deliveryOrderId);
//        Result<List<OutinOrderForReverseResp>> originOutinOrder = bcOtsService.getOrigionOutinOrder(request);
//        if (!originOutinOrder.isSuccess()) {
//            throw new FacadeException("OTS-03-002-15-018");
//        }
        log.warn("MnSapIf004ServiceImpl.externalSyncMaterial bcOtsService.reverseOutinOrder,request:{}",JSON.toJSONString(outin));
        Result<WarehousingOrderOperationResponse> result = bcOtsService.reverseOutinOrder(outin);
        log.warn("MnSapIf004ServiceImpl.externalSyncMaterial bcOtsService.reverseOutinOrder,request:{}",result==null?"":JSON.toJSONString(result));

        if (result.isSuccess()) {
            //通知调拨 销售单 改状态
            String isAllocate = outin.getFeatures().getFeatureMap().get("isAllocate");
            boolean dealOnwayInventory = ("1").equals(isAllocate);
            boolean isSaleOrder = ("2").equals(isAllocate);
            String isSaleChangeOrder=outin.getFeatures().getFeatureMap().get("saleOrderTag");
            if (isSaleOrder) {
                //入库且是换货类型的 不通知销售单
                /**
                 * exchange  在其他应用的参数模型里，不好引用~
                 *  @see com.alibaba.citrus.ots.b2b.trade.order.facade.common.SetOrderTag
                 */
                if(!("exchange".equals(isSaleChangeOrder)&&isInReverse)) {
                    SendOutGoodsRequest updateSaleOrderRequest = getNotifySaleOrderRequest(outin);
                    log.warn("MnSapIf004ServiceImpl.externalSyncMaterial sandOutGoodsService.chargeAgainstGoods,request:{}",JSON.toJSONString(updateSaleOrderRequest));
                    Result<DeliveryOrderModifyResponse> responseResult = sandOutGoodsService.chargeAgainstGoods(updateSaleOrderRequest);
                    log.warn("MnSapIf004ServiceImpl.externalSyncMaterial sandOutGoodsService.chargeAgainstGoods,request:{}",responseResult==null?"":JSON.toJSONString(responseResult));
                    if (!responseResult.isSuccess()) {
                        throw new FacadeException("OTS-03-002-01-15-008", responseResult.getMessage());
                    }
                }
            }
            if (dealOnwayInventory) {
                List<CreateOutinOrderGoodsRequest> subOrderDOS = outin.getGoods();
                String postingTime=outin.getFeatures().getFeatureMap().get("postingTime");
                Result<List<MaterialTypes>> materialResult = otsUnifiedInventoryService.getMaterialTyps();
                List<MaterialTypes> materialTypesList = materialResult.getResult();
                List<AllocateOrderCancelShippedScItemDTO> outlist = getReverseOutList(materialTypesList, subOrderDOS, isAllocate);
                List<AllocateOrderReceivedScItemDTO> inlist = getReverseInList(materialTypesList, subOrderDOS, isAllocate);
                if (outlist.size() > 0) {
                    AllocateOrderCancelShippedRequest request=AllocateOrderCancelShippedRequest.builder()
                        .id(outin.getSourceWarehousingOrderId())
                        .outMaterialCertificateId(sapMaterial.getOutMaterialCertificateId()).postingTime(postingTime)
                        .shippedScItemDTOS(outlist).build();
                    log.warn("MnSapIf004ServiceImpl.externalSyncMaterial allocateOrderWriteService.cancelShipped,request:{}",JSON.toJSONString(request));
                    Result<AllocateOrderCancelShippedResponse> resp = allocateOrderWriteService.cancelShipped(request);
                    log.warn("MnSapIf004ServiceImpl.externalSyncMaterial allocateOrderWriteService.cancelShipped,resp:{}",resp==null?"":JSON.toJSONString(resp));
                    if (!resp.isSuccess()) {
                        throw new FacadeException("OTS-03-002-01-15-004", resp.getMessage());
                    }
                }
                if(inlist.size() >0 ){
                    AllocateOrderCancelReceivedRequest request=AllocateOrderCancelReceivedRequest.builder()
                        .id(outin.getSourceWarehousingOrderId())
                        .outMaterialCertificateId(sapMaterial.getOutMaterialCertificateId()).postingTime(postingTime)
                        .receivedScItemDTOS(inlist).build();
                    log.warn("MnSapIf004ServiceImpl.externalSyncMaterial allocateOrderWriteService.cancelReceived,request:{}",JSON.toJSONString(request));
                    Result<AllocateOrderCancelReceivedResponse> resp = allocateOrderWriteService.cancelReceived(request);
                    log.warn("MnSapIf004ServiceImpl.externalSyncMaterial allocateOrderWriteService.cancelReceived,resp:{}",resp==null?"":JSON.toJSONString(resp));
                    if (!resp.isSuccess()) {
                        throw new FacadeException("OTS-03-002-01-15-009", resp.getMessage());
                    }
                }
            }
        }
        if (!result.isSuccess()) {
            saveRetryLogs(sapMaterial.getOutMaterialCertificateId(), JSON.toJSONString(mnCrmIf004Request));
            throw new FacadeException("OTS-03-002-01-15-012","请前往系统日志搜索并重试接口");
        }
        //如果成功了还需要更新重试表
        if (result.isSuccess()){
            if (isBeforeSourceOrder(outin,sapMaterial)){
                saveMaterialRetry(mnCrmIf004Request,sapMaterial);
            }else {
                updateMaterialRetry(mnCrmIf004Request,sapMaterial);
            }
        }
    }

    //更新物料凭证的重试
    private void updateMaterialRetry(MnSapIf004Request request,MnSapIf004SDO sdo){
        try {
            String outMaterialCertificateId = sdo.getOutMaterialCertificateId();
            String outSourceOrderId = sdo.getOutSourceOrderId();
            String outAllocateOrderId = sdo.getOutAllocateOrderId();
            if (StringUtils.isAllBlank(outSourceOrderId,outAllocateOrderId)){
                return;
            }
            MaterialSyncRetryLoadListRequest materialSyncRetryLoadListRequest = new MaterialSyncRetryLoadListRequest();
            materialSyncRetryLoadListRequest.setOutMaterialCertificateId(outMaterialCertificateId);
            materialSyncRetryLoadListRequest.setStatus(0);
            Result<List<MaterialSyncRetry>> listResult = materialSyncRetryService.loadList(materialSyncRetryLoadListRequest);
            if (listResult != null && CollectionUtils.isNotEmpty(listResult.getResult())){
                for (MaterialSyncRetry materialSyncRetry : listResult.getResult()) {
                    materialSyncRetry.setStatus(1);
                    materialSyncRetryService.update(materialSyncRetry);
                }
            }
        }catch (Throwable e){
            log.error("MnSapIf004ServiceImpl_updateMaterialRetry_error",e);
        }
    }
    /**
     * 004先来的写入临时表 物料凭证唯一
     * @param request
     * @param sdo
     */
    private void saveMaterialRetry(MnSapIf004Request request,MnSapIf004SDO sdo){
        try {
            String outMaterialCertificateId = sdo.getOutMaterialCertificateId();
            String outSourceOrderId = sdo.getOutSourceOrderId();
            String outAllocateOrderId = sdo.getOutAllocateOrderId();
            if (StringUtils.isAllBlank(outSourceOrderId,outAllocateOrderId)){
                return;
            }
            MaterialSyncRetryLoadListRequest materialSyncRetryLoadListRequest = new MaterialSyncRetryLoadListRequest();
            materialSyncRetryLoadListRequest.setOutMaterialCertificateId(outMaterialCertificateId);
            Result<List<MaterialSyncRetry>> listResult = materialSyncRetryService.loadList(materialSyncRetryLoadListRequest);
            if (listResult != null && CollectionUtils.isNotEmpty(listResult.getResult())){
                return;
            }
            MaterialSyncRetry materialSyncRetry = new MaterialSyncRetry();
            materialSyncRetry.setOutMaterialCertificateId(outMaterialCertificateId);
            materialSyncRetry.setOutOrderId(StringUtils.isBlank(outSourceOrderId)?outAllocateOrderId :outSourceOrderId);
            materialSyncRetry.setStatus(0);
            materialSyncRetry.setRequestParams(JSON.toJSONString(request));
            materialSyncRetryService.create(materialSyncRetry);
        }catch (Throwable e){
            log.error("MnSapIf004ServiceImpl_saveMaterialRetry_error",e);
        }

    }

    /**
     * 保存需要重试的物料凭证信息，通过materialId进行幂等，同一个materialId只创建一次
     * @param materialId
     * @param json
     */
    private void saveRetryLogs(String materialId,String json){
        GetFailRecordBySAPMertrailIdRequest queryRequest = new GetFailRecordBySAPMertrailIdRequest();
        queryRequest.setOutMaterialCertificateId(materialId);
        try {
            //如果已经存在，则不需要创建重试记录
            Result<List<MaterialRetry>> result = retryService.getFailRecordBySAPMertrailId(queryRequest);
            if (CollectionUtils.isNotEmpty(result.getResult())){
                return;
            }
        }catch (Exception e){
            log.error("saveRetryLogs query: {} failed", materialId);
        }

        MaterialRetry materialRetry=new MaterialRetry();
        materialRetry.setOutMaterialCertificateId(materialId);
        materialRetry.setRequestJson(json);
        retryService.createFailMaterial(materialRetry);
    }

}
